
// Copyright (c) WanSheng Intelligent Corp. All rights reserved.
// Licensed under the LGPL v3.0 license. 


#include <stdlib.h>
#include <stdbool.h>
#include <linux/prctl.h>
#include "snap7_plugin.h"
#include "wa_plugin_sdk.h"
#include "logs.h"
#include "path.h"
#include "ams_constants.h"
#include "file_util.h"
#include "wa_edge_configs.h"
#include <bitset>
#include <algorithm>
s7_device_t* g_s7_devices[MAX_S7_DEVICE] = { 0 };
wa_plugin_device_info_t* g_devices_info = NULL;

bitset<16>& convert16ByteOrder(bitset<16>& bitFlow)
{
	bitset<16> tmp = bitFlow;
	for (size_t i = 1; i <= 2; i++)
	{
		for (size_t j = 0; j < 8; j++)
		{
			bitFlow[(i - 1) * 8 + j] = tmp[(2 - i) * 8 + j];
		}

	}
	return bitFlow;
}

bitset<32>& convert32ByteOrder(bitset<32>& bitFlow)
{
	bitset<32> tmp = bitFlow;
	for (size_t i = 1; i <= 4; i++)
	{
		for (size_t j = 0; j < 8; j++)
		{
			bitFlow[(i - 1) * 8 + j] = tmp[(4 - i) * 8 + j];
		}

	}
	return bitFlow;
}

bitset<64>& convert64ByteOrder(bitset<64>& bitFlow)
{
	bitset<64> tmp = bitFlow;
	for (size_t i = 1; i <= 8; i++)
	{
		for (size_t j = 0; j < 8; j++)
		{
			bitFlow[(i - 1) * 8 + j] = tmp[(8 - i) * 8 + j];
		}

	}
	return bitFlow;
}

void reverseBytes(std::uint8_t* data, size_t size) {
	std::reverse(reinterpret_cast<char*>(data), reinterpret_cast<char*>(data) + size);
}

static int get_type_name(const char* type)
{
	WARNING2("Enter..[%s]", type);
	if (strcmp(type, "Bit") == 0)
		return T_Bits;
	else if (strcmp(type, "Int8") == 0)
		return T_int8;
	else if (strcmp(type, "Uint8") == 0)
		return T_Uint8;
	else if (strcmp(type, "Int16") == 0)
		return T_int16;
	else if (strcmp(type, "Uint16") == 0)
		return T_Uint16;
	else if (strcmp(type, "Int32") == 0)
		return T_int32;
	else if (strcmp(type, "Uint32") == 0)
		return T_Uint32;
	else if (strcmp(type, "Int64") == 0)
		return T_int64;
	else if (strcmp(type, "Uint64") == 0)
		return T_Uint64;
	else if (strcmp(type, "Float") == 0)
		return T_Float;
	else if (strcmp(type, "Double") == 0)
		return T_Double;
	else
		return T_Invalid_DataType;
}

static int get_area_name(const char* area)
{
	WARNING2("Enter..[%s]", area);
	if (strcmp(area, "DB") == 0)
		return S7AreaDB;
	else if (strcmp(area, "PE") == 0)
		return S7AreaPE;
	else if (strcmp(area, "PA") == 0)
		return S7AreaPA;
	else if (strcmp(area, "MK") == 0)
		return S7AreaMK;
	else if (strcmp(area, "CT") == 0)
		return S7AreaCT;
	else if (strcmp(area, "TM") == 0)
		return S7AreaTM;
	else
		return -1;
}

//static int get_data_type_size(s7_dt_t dt)
//{
//	switch (dt)
//	{
//	case T_Bits:
//	case T_int8:
//	case T_Uint8:
//		return 1;
//
//	case T_int16:
//	case T_Uint16:
//		return 2;
//
//	case T_int32:
//	case T_Uint32:
//	case T_Float:
//		return 4;
//
//	case T_int64:
//	case T_Uint64:
//	case T_Double:
//		return 8;
//
//	default:
//		return 0;
//	}
//}

s7_device_t* find_device(const char* name)
{
	WARNING2("Enter..");
	int i;
	for (i = 0; i < MAX_S7_DEVICE; i++)
	{
		if (g_s7_devices[i] && strcmp(name, g_s7_devices[i]->plugin_cfg->device_name) == 0)
		{
			return g_s7_devices[i];
		}
	}

	return NULL;
}

static s7_device_t* parse_customer_config_A(wa_plugin_device_info_t* device_info)
{
	WARNING2("Enter..");
	int useSlot, useRack, useType;

	if (device_info == NULL)
		return NULL;
	if (device_info->custom_cfg == NULL)
		return NULL;
	cJSON* custom_cfg = cJSON_Parse(device_info->custom_cfg);
	if (custom_cfg == NULL)
		return NULL;
	if (!cJSON_IsObject(custom_cfg))
	{
		cJSON_Delete(custom_cfg);
		return NULL;
	}



	const char* ip = cJSONx_GetObjectString(custom_cfg, "ip");
	const char* sz_useRack = cJSONx_GetObjectString(custom_cfg, "rack");
	const char* sz_useSlot = cJSONx_GetObjectString(custom_cfg, "slot");
	const char* sz_useType = cJSONx_GetObjectString(custom_cfg, "ConnectionType");

	WARNING2("The PARA:IP[%s],机架[%s],卡槽[%s],连接类型[%s]", ip, sz_useRack, sz_useSlot, sz_useType);

	if (ip == NULL || sz_useRack == NULL || sz_useSlot == NULL)
	{
		WARNING("[%s] ip or rack or slot error!\n", device_info->device_name);
		cJSON_Delete(custom_cfg);
		return NULL;
	}

	useSlot = atoi(sz_useSlot);
	useRack = atoi(sz_useRack);
	useType = atoi(sz_useType);


	s7_device_t* s7_device = (s7_device_t*)malloc(sizeof(s7_device_t));
	memset(s7_device, 0, sizeof(s7_device_t));

	s7_device->ip = strdup(ip);
	s7_device->rack = useRack;

	s7_device->slot = useSlot;
	s7_device->type = useType;
	s7_device->plugin_cfg = device_info;

	cJSON_Delete(custom_cfg);

	return s7_device;
}

int set_device(s7_device_t* device)
{
	WARNING2("Enter..");
	int i;
	for (i = 0; i < MAX_S7_DEVICE; i++)
	{
		if (g_s7_devices[i] == NULL)
		{
			g_s7_devices[i] = device;

			return i;
		}
	}

	return -1;
}

void open_s7_device(s7_device_t* device)
{
	WARNING2("Enter..");
	TS7Client* dc;

	dc = new TS7Client();
	if (dc)
	{

		int res = dc->SetConnectionType(device->type);
		if (res == 0)
		{
			WARNING("[%s] PLC has SetConnectionType!\n", device->plugin_cfg->device_name);
		}

		res = dc->ConnectTo(device->ip.c_str(), device->rack, device->slot);
		if (res == 0)
		{
			WARNING("[%s] PLC has Connected!\n", device->plugin_cfg->device_name);
		}
		else
		{
			WARNING("[%s] Cannot connect PLC! The code is [%X]\n", device->plugin_cfg->device_name, res);
			delete dc;
			return;
		}

		device->dc = dc;
	}


	return;
}

bool load_device_config(const char* plugin_name)
{
	WARNING2("Enter..");
	char buf[256];
	int device_num = 0;
	WARNING("Start loading s7 config...");

	assert(g_devices_info == NULL);

	wa_plugin_device_info_t* devices_info = wa_plugin_load_device_info_A(plugin_name, &device_num);

	if (devices_info == NULL)
	{
		WARNING2("load cfg fail");
		return false;
	}

	for (int k = 0; k < device_num; k++)
	{
		wa_plugin_device_info_t* info = &devices_info[k];
		if (find_device(info->device_name))
		{
			WARNING2("device %s already add. skip current.", info->device_name);
			continue;
		}
		WARNING2("bus_info: %p, tcp_addr: %p\n", info->bus_info, &info->bus_info->tcp_addr);
		s7_device_t* s7 = parse_customer_config_A(info);
		if (s7 == NULL)
		{
			continue;
		}

		set_device(s7);
	}

	g_devices_info = devices_info;

	WARNING("Finished loading s7 plugin config\n");

	return true;
}


static char* parse_s7_cmd_query(char* query, int* AREA, int* area_Number, int* start_address, int* items, int* type, int* CovOrder)
{
	WARNING2("Enter..[%s]", query);
	char buf[128] = { 0 };
	char name[128] = { 0 };
	int len = strlen(query);
	if (!find_key_value(query, len, "DI", name, sizeof(name), '&'))
	{
		WARNING2("No have DI");
		return NULL;
	}

	if (!find_key_value(query, len, "Area", buf, sizeof(buf), '&'))
	{
		WARNING2("No have Area");
		return NULL;
	}
	*AREA = get_area_name(buf);
	if (*AREA == -1)
	{
		WARNING2("Area is invalid");
		return NULL;
	}
	if (!find_key_value(query, len, "Start", buf, sizeof(buf), '&'))
	{
		WARNING2("No have Start");
		return NULL;
	}
	*start_address = atoi(buf);


	if (find_key_value(query, len, "DBNumber", buf, sizeof(buf), '&'))
		*area_Number = atoi(buf);
	else
	{
		WARNING2("No have DBNumber");
		return NULL;
	}

	if (items)
	{
		if (find_key_value(query, len, "ITEMS", buf, sizeof(buf), '&'))
			*items = atoi(buf);
		else
			*items = 1;
	}
	if (CovOrder)
	{
		if (find_key_value(query, len, "CovOrder", buf, sizeof(buf), '&'))
			*CovOrder = atoi(buf);
		else
			*CovOrder = 0;
	}

	if (!find_key_value(query, len, "Datatype", buf, sizeof(buf), '&'))
	{
		WARNING2("No have Datatype");
		return NULL;
	}
	*type = get_type_name(buf);

	if ((s7_dt_t)*type == T_Invalid_DataType)
	{
		WARNING2("Datatype is invalid");
		return NULL;
	}

	return strdup(name);
}

s7_device_t* find_device_open(const char* name)
{
	WARNING2("Enter..");
	s7_device_t* device = find_device(name);
	if (device && device->dc)
	{
		return device;
	}

	return NULL;
}

void snap7_Get(restful_request_t* request, REQ_ENV_HANDLE req_env)
{
	WARNING2("Enter..");
	cJSON* device_data;
	const char* payload;
	int res;

	if (request->query == NULL)
	{
		WARNING2("query is NULL");
		return;
	}
	int AREA, area_Number, start_address, items, CovOrder = 0;
	int type = T_Invalid_DataType;

	s7_device_t* device;
	char* name = parse_s7_cmd_query(request->query, &AREA, &area_Number, &start_address, &items, &type, &CovOrder);
	WARNING2("当前参数：[%d],[%d],[%d],[%d]", AREA, area_Number, start_address, type);

	if (name == NULL)
	{
		WARNING2("parse_s7_cmd_query have error");
		goto end;
	}


	device = find_device_open(name);
	if (device == NULL)
	{
		WARNING2("find_device_open have error");
		goto end;
	}

	device_data = cJSON_CreateArray();
	switch (type)
	{
	case T_Bits:
	{
		for (int b = 0; b < items; b++)
		{
			bool tmp;
			res = device->dc->ReadArea(AREA, area_Number, start_address, 1, S7WLBit, &tmp);
			if (res == 0)
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(tmp));
		}
	}
	break;
	case T_int8:
	{
		for (int b = 0; b < items; b++)
		{
			int8_t tmp;
			res = device->dc->ReadArea(AREA, area_Number, start_address, 1, S7WLByte, &tmp);
			if (res == 0)
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(tmp));
		}
	}
	break;
	case T_Uint8:
	{
		for (int b = 0; b < items; b++)
		{
			uint8_t tmp;
			res = device->dc->ReadArea(AREA, area_Number, start_address, 1, S7WLByte, &tmp);
			if (res == 0)
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(tmp));
		}
	}
	break;
	case T_int16:
	{
		for (int b = 0; b < items; b++)
		{
			bitset<16> bits;
			res = device->dc->ReadArea(AREA, area_Number, start_address, 2, S7WLByte, &bits);
			double tmp;
			if (CovOrder)
				convert16ByteOrder(bits);

			tmp = *reinterpret_cast<short int*>(&bits);
			if (res == 0)
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(tmp));
		}
	}
	break;
	case T_Uint16:
	{
		for (int b = 0; b < items; b++)
		{
			bitset<16> bits;
			if (AREA == S7AreaCT)
			{
				res = device->dc->ReadArea(AREA, area_Number, start_address, 1, S7WLCounter, &bits);
			}
			else if (AREA == S7AreaTM)
			{
				res = device->dc->ReadArea(AREA, area_Number, start_address, 1, S7WLTimer, &bits);
			}
			else
			{
				res = device->dc->ReadArea(AREA, area_Number, start_address, 2, S7WLByte, &bits);
			}
			double tmp;
			if (CovOrder)
				convert16ByteOrder(bits);
			tmp = *reinterpret_cast<unsigned short int*>(&bits);
			if (res == 0)
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(tmp));
		}
	}
	break;
	case T_int32:
	{
		for (int b = 0; b < items; b++)
		{
			bitset<32> bits;
			res = device->dc->ReadArea(AREA, area_Number, start_address, 4, S7WLByte, &bits);
			double tmp;
			if (CovOrder)
				convert32ByteOrder(bits);
			tmp = *reinterpret_cast<int*>(&bits);
			if (res == 0)
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(tmp));
		}
	}
	break;
	case T_Uint32:
	{
		for (int b = 0; b < items; b++)
		{
			bitset<32> bits;
			res = device->dc->ReadArea(AREA, area_Number, start_address, 4, S7WLByte, &bits);
			double tmp;
			if (CovOrder)
				convert32ByteOrder(bits);
			tmp = *reinterpret_cast<unsigned int*>(&bits);
			if (res == 0)
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(tmp));
		}
	}
	break;
	case T_Float:
	{
		for (int b = 0; b < items; b++)
		{
			bitset<32> bits;
			res = device->dc->ReadArea(AREA, area_Number, start_address, 4, S7WLByte, &bits);
			double tmp;
			if (CovOrder)
				convert32ByteOrder(bits);
			tmp = *reinterpret_cast<float*>(&bits);
			WARNING2("!!!!![%f]", tmp);
			if (res == 0)
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(tmp));
		}
	}
	break;
	case T_int64:
	{
		for (int b = 0; b < items; b++)
		{
			bitset<64> bits;
			res = device->dc->ReadArea(AREA, area_Number, start_address, 8, S7WLByte, &bits);
			double tmp;
			if (CovOrder)
				convert64ByteOrder(bits);
			tmp = *reinterpret_cast<int64_t*>(&bits);
			if (res == 0)
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(tmp));
		}
	}
	break;
	case T_Uint64:
	{
		for (int b = 0; b < items; b++)
		{
			bitset<64> bits;
			res = device->dc->ReadArea(AREA, area_Number, start_address, 8, S7WLByte, &bits);
			double tmp;
			if (CovOrder)
				convert64ByteOrder(bits);
			tmp = *reinterpret_cast<uint64_t*>(&bits);
			if (res == 0)
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(tmp));
		}
	}
	break;
	case T_Double:
	{
		for (int b = 0; b < items; b++)
		{
			bitset<64> bits;
			res = device->dc->ReadArea(AREA, area_Number, start_address, 8, S7WLByte, &bits);
			double tmp;
			if (CovOrder)
				convert64ByteOrder(bits);
			tmp = *reinterpret_cast<double*>(&bits);
			if (res == 0)
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(tmp));
		}
	}
	break;
	}

	if (res != 0)
	{
		WARNING2("读取失败,返回值 [%X]", res);
		goto end;
	}

	payload = (const char*)cJSON_PrintUnformatted(device_data);
	wa_req_env_handover_payload(req_env, CONTENT_2_05, IA_APPLICATION_JSON, strlen(payload), &payload);
	cJSON_Delete(device_data);
	free(name);

	return;

end:
	wa_set_response_status(req_env, INTERNAL_SERVER_ERROR_5_00);
	if (name)
		free(name);
	if (device_data)
		cJSON_Delete(device_data);

	return;
}

void snap7_Put(restful_request_t* request, REQ_ENV_HANDLE req_env)
{
	WARNING2("Enter..");
	int res = 0;
	int code = BAD_REQUEST_4_00;
	if (request->query == NULL || request->payload_len == 0)
	{
		WARNING2("request is invalid");
		wa_set_response_status(req_env, code);
		return;
	}
	char* payload = request->payload;
	int AREA, area_Number, start_address;
	int type, CovOrder;
	s7_device_t* device;
	cJSON* rootvalues;


	char* name = parse_s7_cmd_query(request->query, &AREA, &area_Number, &start_address, NULL, &type, &CovOrder);
	WARNING2("当前参数：[%d],[%d],[%d],[%d]", AREA, area_Number, start_address, type);

	auto writeFun = [&](auto tmp)
	{
		switch (type)
		{
		case T_Bits:
		{
			auto value = static_cast<bool>(tmp);
			res = device->dc->WriteArea(AREA, area_Number, start_address, 1, S7WLBit, &value);
		}
		break;
		case T_int8:
		{
			auto value = static_cast<int8_t>(tmp);
			res = device->dc->WriteArea(AREA, area_Number, start_address, 1, S7WLByte, &value);
		}
		break;
		case T_Uint8:
		{
			auto value = static_cast<uint8_t>(tmp);
			res = device->dc->WriteArea(AREA, area_Number, start_address, 1, S7WLByte, &value);
		}
		break;
		case T_int16:
		{
			auto value = static_cast<int16_t>(tmp);
			bitset<16> bits(value);
			if (CovOrder)
			{
				convert16ByteOrder(bits);
			}
			res = device->dc->WriteArea(AREA, area_Number, start_address, 2, S7WLByte, &bits);
		}
		break;
		case T_Uint16:
		{
			auto value = static_cast<uint16_t>(tmp);
			bitset<16> bits(value);
			if (CovOrder)
			{
				convert16ByteOrder(bits);
			}
			res = device->dc->WriteArea(AREA, area_Number, start_address, 2, S7WLByte, &bits);
		}
		break;
		case T_int32:
		{
			auto value = static_cast<int32_t>(tmp);
			bitset<32> bits(value);
			if (CovOrder)
			{
				convert32ByteOrder(bits);
			}
			res = device->dc->WriteArea(AREA, area_Number, start_address, 4, S7WLByte, &bits);
		}
		break;
		case T_Uint32:
		{
			auto value = static_cast<uint32_t>(tmp);
			bitset<32> bits(value);
			if (CovOrder)
			{
				convert32ByteOrder(bits);
			}
			res = device->dc->WriteArea(AREA, area_Number, start_address, 4, S7WLByte, &bits);
		}
		break;
		case T_Float:
		{
			auto value = static_cast<float>(tmp);
			std::uint8_t bytes[sizeof(value)];
			std::memcpy(bytes, &value, sizeof(value));
			if (CovOrder)
			{
				reverseBytes(bytes, sizeof(value));
			}
			res = device->dc->WriteArea(AREA, area_Number, start_address, 4, S7WLByte, &bytes);
		}
		break;
		case T_int64:
		{
			auto value = static_cast<int64_t>(tmp);
			bitset<64> bits(value);
			if (CovOrder)
			{
				convert64ByteOrder(bits);
			}
			res = device->dc->WriteArea(AREA, area_Number, start_address, 8, S7WLByte, &bits);
		}
		break;
		case T_Uint64:
		{
			auto value = static_cast<uint64_t>(tmp);
			bitset<64> bits(value);
			if (CovOrder)
			{
				convert64ByteOrder(bits);
			}
			res = device->dc->WriteArea(AREA, area_Number, start_address, 8, S7WLByte, &bits);
		}
		break;
		case T_Double:
		{
			auto value = static_cast<double>(tmp);
			std::uint8_t bytes[sizeof(value)];
			std::memcpy(bytes, &value, sizeof(value));
			if (CovOrder)
			{
				reverseBytes(bytes, sizeof(value));
			}
			res = device->dc->WriteArea(AREA, area_Number, start_address, 8, S7WLByte, &bytes);
		}
		break;
		}
	};

	if (name == NULL)
	{
		WARNING2("parse_s7_cmd_query have error");
		goto end;
	}

	device = find_device_open(name);
	if (device == NULL)
	{
		WARNING2("find_device_open have error");
		goto end;
	}



	if (request->payload_fmt == IA_TEXT_PLAIN)
	{
		if (type == T_Float || type == T_Double)
		{
			double value2 = stod(payload);
			writeFun(value2);
		}
		else
		{
			long long value2 = stoll(payload);
			writeFun(value2);
		}
	}
	else if (request->payload_fmt == IA_APPLICATION_JSON)
	{
		rootvalues = cJSON_Parse((const char*)payload);
		if (!cJSON_IsArray(rootvalues))
		{
			WARNING2("payload is not Array");
			goto end;
		}
		cJSON* item;
		cJSON_ArrayForEach(item, rootvalues)
		{
			if (!cJSON_IsNumber(item))
			{
				WARNING2("item is not Number");
				goto end;
			}
			if (type == T_Float || type == T_Double)
			{
				double value2 = item->valuedouble;
				writeFun(value2);
			}
			else
			{
				int value2 = item->valueint;
				writeFun(value2);
			}
		}
	}
	else
	{
		WARNING2("payload_fmt is invalid");
		goto end;
	}

	if (res == 0)
	{
		code = CHANGED_2_04;
	}
	else
	{
		WARNING2("写入失败,返回值 [%X]", res);
		code = INTERNAL_SERVER_ERROR_5_00;
		goto end;
	}

	free(name);
	cJSON_Delete(rootvalues);
	wa_set_response_status(req_env, code);
	return;

end:

	if (rootvalues != NULL)
		cJSON_Delete(rootvalues);
	if (name)
		free(name);

	wa_set_response_status(req_env, code);

	return;

}

